#pragma once

#include "../mqcommon/mq_helper.hpp"
#include "../mqcommon/mq_logger.hpp"

#include <iostream>
#include <string>
#include <functional>
#include <memory>
#include <cassert>
#include <sstream>
#include <mutex>
#include <unordered_map>

namespace mq
{
    struct Binding
    {
        using ptr = std::shared_ptr<Binding>;
        std::string exchange_name; // 交换机名称
        std::string msgqueue_name; // 队列名称
        std::string binding_key;

        Binding() {}
        Binding(const std::string &ename, const std::string &qname, const std::string &key)
            : exchange_name(ename), msgqueue_name(qname), binding_key(key) {}
    };

    // 一个交换机可以绑定多个队列，而队列和绑定信息是一一对应的。
    using MsgQueueBindingMap = std::unordered_map<std::string, Binding::ptr>;
    using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;
    class BindingMapper
    {
    private:
        SqliteHelper _sql_helper;

    private:
        static int selectCallBack(void *arg, int numcol, char **row, char **fields)
        {
            BindingMap *bmap = static_cast<BindingMap *>(arg);

            // 构建一个绑定信息类对象
            Binding::ptr bp = std::make_shared<Binding>(row[0], row[1], row[2]);

            // 交换机绑定的队列map, 存在及获取，不存在及创建
            MsgQueueBindingMap &qmap = (*bmap)[row[0]];

            // 将绑定信息类对象插入进队列map中
            qmap.insert({row[1], bp});

            return 0;
        }

    public:
        BindingMapper(const std::string &dbfile) : _sql_helper(dbfile)
        {
            // 获取父级目录
            const std::string &path = FileHelper::parentDirectory(dbfile);
            // 创建父级目录
            FileHelper::createDirectory(path);
            //创建数据库文件
            _sql_helper.open();
            // 创建数据表
            createTable();
        }
        ~BindingMapper()
        {
            _sql_helper.close();
        }
        void createTable()
        {
            std::stringstream sql;
            sql << "create table if not exists binding_table(";
            sql << "exchange_name varchar(32), ";
            sql << "msgqueue_name varchar(32), ";
            sql << "binding_key varchar(128));";
            assert(_sql_helper.exec(sql.str(), nullptr, nullptr));
        }
        void removeTable()
        {
            std::string sql = "drop table if exists binding_table;";
            assert(_sql_helper.exec(sql, nullptr, nullptr));
        }
        bool insert(Binding::ptr &bp)
        {
            std::stringstream sql;
            sql << "insert into binding_table values (";
            sql << "'" << bp->exchange_name << "',";
            sql << "'" << bp->msgqueue_name << "',";
            sql << "'" << bp->binding_key << "');";
            return (_sql_helper.exec(sql.str(), nullptr, nullptr));
        }
        void remove(const std::string &ename, const std::string &qname)
        {
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name='" << ename << "' and ";
            sql << "msgqueue_name='" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void removeExchangeBindings(const std::string &ename)
        {
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "exchange_name = '" << ename << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        void removeMsgQueueBindings(const std::string &qname)
        {
            std::stringstream sql;
            sql << "delete from binding_table where ";
            sql << "msgqueue_name = '" << qname << "';";
            _sql_helper.exec(sql.str(), nullptr, nullptr);
        }
        BindingMap recovery()
        {
            BindingMap bmap;
            std::string sql = "select exchange_name, msgqueue_name, binding_key from binding_table;";
            _sql_helper.exec(sql, selectCallBack, &bmap);
            return bmap;
        }
    };

    class BindingManager
    {
    private:
        std::mutex _mutex;
        BindingMapper _mapper;
        BindingMap _bindings;

    public:
    using ptr = std::shared_ptr<BindingManager>;
        BindingManager(const std::string &dbfile) : _mapper(dbfile)
        {
            _bindings = _mapper.recovery();
        }
        bool bind(const std::string &ename, const std::string &qname, const std::string &key, bool durable)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            if (eit != _bindings.end() && (eit->second.find(qname) != eit->second.end()))
            {   
                //绑定数据已存在
                return true;
            }

            Binding::ptr bp = std::make_shared<Binding>(ename, qname, key);
            // 当交换机和队列的持久化标志位都为true,绑定数据才进行持久化,这个durable由外界判断后传入
            if (durable == true)
            {
                bool ret = _mapper.insert(bp);
                if (ret == false)
                {
                    return false;
                }
            }
            //存在及获取，不存在及创建
            auto &MsgQueueMap = _bindings[ename];
            MsgQueueMap.insert({qname, bp});
            return true;
        }
        void unbind(const std::string &ename, const std::string &qname)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            //没有交换机信息，直接退出
            if(eit == _bindings.end()) { return; }
            auto qit = eit->second.find(qname);
            //没有队列相关信息，直接退出
            if(qit == eit->second.end()){ return ;}

            //判断持久化太麻烦了,所以这里不管持久化标志是否存在,我们直接去数据库中删除
            _mapper.remove(ename,qname);
            _bindings[ename].erase(qname);
        }
        //删除指定交换机的所有绑定数据 ---当删除交换机时需要删除交换机相关的所有绑定信息
        void removeExchangeBindngs(const std::string &ename){
            //同样的这里不判断持久化，直接操作
            _mapper.removeExchangeBindings(ename);
            auto eit = _bindings.find(ename);
            if(eit == _bindings.end()){
                //不存在直接return
                return;
            }

            _bindings.erase(eit);
        }
        void removeMsgQueueBindings(const std::string &qname){
            std::unique_lock<std::mutex> lock(_mutex);
            //同样的这里不判断持久化，直接操作
            _mapper.removeMsgQueueBindings(qname);
            //遍历所有的交换机，因为一个交换机可以绑定多个队列，这个要删除的队列可能绑定了多个交换机
            for(auto eit = _bindings.begin(); eit != _bindings.end(); eit++){
                //eit->second是一个MsgQueueMap,这里判断的是这个队列是否存在这个map中，存在就删除
                auto qit = eit->second.find(qname);
                if(qit != eit->second.end()){
                    eit->second.erase(qit);
                }
            }
        }
        //获取交换机绑定的队列描述信息，当交换机收到消息时，需要将消息转给绑定的队列
        MsgQueueBindingMap getExchangeBindings(const std::string &ename){
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
                if (eit == _bindings.end()) {
                return MsgQueueBindingMap();
            }

            return eit->second; 
        }
        Binding::ptr getBinding(const std::string &ename, const std::string &qname){
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            //没有交换机信息，直接退出
            if(eit == _bindings.end()) { return Binding::ptr(); }
            auto qit = eit->second.find(qname);
            //没有队列相关信息，直接退出
            if(qit == eit->second.end()){ return Binding::ptr();}

            return qit->second;
        }
        bool exists(const std::string &ename, const std::string &qname){
            std::unique_lock<std::mutex> lock(_mutex);
            auto eit = _bindings.find(ename);
            //没有交换机信息，直接退出
            if(eit == _bindings.end()) { return false; }
            auto qit = eit->second.find(qname);
            //没有队列相关信息，直接退出
            if(qit == eit->second.end()){ return false;}

            return true;
        }
        size_t size(){
            std::unique_lock<std::mutex> lock(_mutex);
            size_t total_size = 0;
            for(auto start = _bindings.begin(); start != _bindings.end() ; start++){
                total_size += start->second.size();
            }

            return total_size;
        }
        void clear(){
            std::unique_lock<std::mutex> lock(_mutex);
            _mapper.removeTable();
            _bindings.clear();
        }
    };
}